#!/usr/bin/env python

def isnum(s):
    if len(s) == 0: return False
    if len(s) > 1 and s[0] == '-':
        s = s[1:]

    valid = True
    for c in s:
        if c not in '0123456789':
            valid = False
    
    return valid

def listinsertextend(l, pos, l2):
    '''
        insert all the elements of l2 in the middle of
        l at position pos
    '''
    for item in l2:
        l.insert(pos, item)
        pos += 1

def listreplace(l, before, after):
    while 1:
      try:
          l.index(before)
      except:
          break

      end = 0
      for index, item in enumerate(l):
          if end: break
          
          if item == before:
              l.remove(before)
              for aindex, aitem in enumerate(after):
                  l.insert(index + aindex, aitem)
                  end = 1

def ssformat(source):
    return [line.lower()
            for line in source.replace('\t',' ').replace('\n',' ').strip().split(' ')
            if line != '']

import sys, os.path
def include_files(source):
    old = source
    
    buff = {}
    for line in source.split('\n'):
        if line.startswith('#include '):
            filename = ''.join(line.split(' ')[1:])
            
            if filename.startswith('<') and filename.endswith('>'):
                newfilename = filename[1:-1]                 # remove < and >
                directory, prog = os.path.split(sys.argv[0]) # get the program path
                lib = os.path.join(directory, 'lib')         # get the lib path
                
                newfilename = os.path.join(lib, newfilename) # join it with the filename
                buff[filename] = open(newfilename).read()
            else:
                buff[filename] = open(filename).read()
    
    for filename in buff:
        source = source.replace('#include ' + filename, buff[filename])
    
    if source != old:
        return include_files(source)
    else:
        return source

import re
def remove_comments(text):
    """ remove c-style comments.
        text: blob of text with comments (can include newlines)
        returns: text with comments removed

        snipped from [http://www.saltycrane.com/blog/2007/11/remove-c-comments-python/]
    """
    pattern = r"""
                            ##  --------- COMMENT ---------
           /\*              ##  Start of /* ... */ comment
           [^*]*\*+         ##  Non-* followed by 1-or-more *'s
           (                ##
             [^/*][^*]*\*+  ##
           )*               ##  0-or-more things which don't start with /
                            ##    but do end with '*'
           /                ##  End of /* ... */ comment
         |                  ##  -OR-  various things which aren't comments:
           (                ## 
                            ##  ------ " ... " STRING ------
             "              ##  Start of " ... " string
             (              ##
               \\.          ##  Escaped char
             |              ##  -OR-
               [^"\\]       ##  Non "\ characters
             )*             ##
             "              ##  End of " ... " string
           |                ##  -OR-
                            ##
                            ##  ------ ' ... ' STRING ------
             '              ##  Start of ' ... ' string
             (              ##
               \\.          ##  Escaped char
             |              ##  -OR-
               [^'\\]       ##  Non '\ characters
             )*             ##
             '              ##  End of ' ... ' string
           |                ##  -OR-
                            ##
                            ##  ------ ANYTHING ELSE -------
             .              ##  Anything other char
             [^/"'\\]*      ##  Chars which doesn't start a comment, string
           )                ##    or escape
    """
    regex = re.compile(pattern, re.VERBOSE|re.MULTILINE|re.DOTALL)
    noncomments = [m.group(2) for m in regex.finditer(text) if m.group(2)]

    out = "".join(noncomments)
    out2 = ''
    
    #Cut out // style comments
    for line in out.split('\n'):
        slash_index = line.find('//')
        if slash_index != -1:
            out2 += line[:slash_index] + '\n'
        else:
            out2 += line + '\n'
    
    return out2

def convert_escapes(source):
    escape_chars = {
        '\\n'  : '\n',
        '\\t'  : '\t',
        '\\0'  : '0',
        '\\\n' : ''
    }
    
    for char in escape_chars:
        source = source.replace(char, escape_chars[char])

    return source

def string2ss(s):
    out = ' '
    for char in s[::-1]:
        out += str(ord(char)) + ' '
    
    return out

def convert_strings(source):
    out = ''
    buff = ''

    capture = False
    for char in source:
        if char == '"':
            if capture:
                out += string2ss(buff)
                buff = ''

            capture = not capture
            continue

        if capture:
            buff += char
        else:
            out += char

    return out

def get_macros(source):
    out = ''
    macros = []
    for line in source.split('\n'):
        if line.startswith('#define'):
            macro = [line
                     for line in line.strip().replace('\t',' ').split(' ')
                     if line != '']
            before, after = macro[1], macro[2:]
            macros.append( [before, after] )
        elif line:
            out += line + '\n'

    return out, macros

def apply_macros(prog, macros):
    old = prog[:]
    
    for before, after in macros:
        listreplace(prog, before, after)
    
    if prog != old:
        return apply_macros(prog, macros)
    else:
        return prog

def source2ss(source):
    source = include_files(source)
    source = remove_comments(source)
    source = convert_escapes(source)
    source = convert_strings(source)
    
    source, macros = get_macros(source)

    prog = ssformat(source)
    
    apply_macros(prog, macros)
    
    return prog

class SSExit(Exception): pass

import sys, random
class SSInterpreter:
    def __init__(self, debug = 0):
        self.stack  = []
        self.debug = debug
    
    def execute(self, prog):
        pc = 0
        jump_stack = []

        #Shorthand marcos for pushing and popping to the stack
        #We do these alot
        pop  = self.stack.pop
        push = self.stack.append

        while pc < len(prog):
            inst = prog[pc]

            if self.debug: pc, '\t', inst,

            #Numbers
            if isnum(inst):  push(int(inst))

            #Math
            elif inst == 'add':                       push(pop() + pop())
            elif inst == 'sub': a, b = pop(), pop() ; push(b - a)
            elif inst == 'mul':                       push(pop() * pop())
            elif inst == 'div': a, b = pop(), pop() ; push(b / a)
            elif inst == 'mod': a, b = pop(), pop() ; push(b % a)

            #Logic
            elif inst == 'and': push(int(bool( pop() and pop() )))
            elif inst == 'or':  push(int(bool( pop() or  pop() )))
            elif inst == 'not': push(int(bool(       not pop() )))
            
            elif inst == 'xor': push(int(bool(pop()) != bool(pop()) ))

            #I/O
            elif inst == 'output': sys.stdout.write(str(pop()) + ' ')
            elif inst == 'input':
                inp = raw_input('?')
                if isnum(inp):
                    push(int(inp))
                else:
                    push(0)
            elif inst == 'outputascii': sys.stdout.write(chr(pop()))
            elif inst == 'inputascii':  self.stack.extend(map(ord, raw_input(''))[::-1])

            #Stack manipulation
            elif inst == 'pop':    pop()
            elif inst == 'swap':   a, b = pop(), pop() ; self.stack.extend([a, b])
            elif inst == 'cycle':  self.stack.insert(0, pop())
            elif inst == 'rcycle': push(self.stack[0]) ; del self.stack[0]
            elif inst == 'dup':    a = pop() ; push(a) ; push(a)
            elif inst == 'clear':  self.stack = []

            #Flow
            elif inst == 'while':
                if self.stack != [] and self.stack[-1] != 0:
                    jump_stack.append(pc - 1)
                else:
                    loop = 1
                    while loop:
                        pc += 1
                        inst = prog[pc]
                        if inst == 'while':
                            loop += 1
                        elif inst == 'wend':
                            loop -= 1
            elif inst == 'wend':
                pc = jump_stack.pop()
            elif inst == 'quit': raise SSExit()
            
            #Misc
            elif inst == 'random': push(random.randrange(pop()))
            elif inst == 'debug':  print self.stack
            
            if self.debug: '\t', self.stack
            pc += 1

        return self.stack

def interactive(debug = 0):
    ss = SSInterpreter(debug)
    print 'Super Stack! v3'
    while 1:
        prog = source2ss(raw_input('> '))
        try:
            print ss.execute(prog) ; print
        except SSExit:
            break
        except IndexError:
            print 'Error: Pop from empty stack.'
        except KeyboardInterrupt:
            print 'Error: Program stopped by user.'

def interpret(prog, debug = 0):
    ss = SSInterpreter(debug)
    if debug: print prog
    try:
        ss.execute(prog)
    except SSExit:
        pass
    except IndexError:
        print 'Error: Pop from empty stack.'
        return
    except KeyboardInterrupt:
        print 'Error: Program stopped by user.'
        return

def load_file(filename):
    f = open(filename)
    prog = source2ss(f.read())
    f.close()
    
    return prog

from optparse import OptionParser
def main():
    parser = OptionParser()
    
    parser.add_option('-c', '--compile',
                     action='store_true', dest='compile',
                     default=False,
                     help='Choose to use GCC to compile your program.')
    
    parser.add_option('-d', '--debug',
                     action='store_true', dest='debug',
                     default=False,
                     help='Debug!')
    
    parser.add_option('-o', '--output',
                     action='store', type='string', dest='output',
                     default='a.out',
                     help='Filename you want to output the compiled file to.')
    
    options, args = parser.parse_args()

    if len(args) == 0:
        interactive(options.debug)
    else:
        prog = load_file(args[0])
        
        if options.compile:
            comp(prog, options.output, options.debug)
        else:
            interpret(prog, options.debug)

if __name__ == "__main__": main()
